use std::process::ExitCode;

use anyhow::anyhow;
use anyhow::Result;
use clap::Parser;
use clap::Subcommand;
use cnf::LOGO;

use commands::his::HisCommandArguments;
use commands::qbt::QbtCommandArguments;
use commands::qtr::QtrCommandArguments;
use commands::sse::SseCommandArguments;
use commands::sync::SyncCommandArguments;
use commands::syns::SynsCommandArguments;

use log::error;
mod commands;
mod jobs;

pub mod cnf;
#[macro_use]
pub mod mac;

use crate::cli::cnf::DEBUG_BUILD_WARNING;
const INFO: &str =
    "
To get started using FudaCraw, and for guides on connecting to and building applications
on top of FudaCraw, check out the FudaCraw documentation (https://surrealdb.com/docs).

If you have questions or ideas, join the FudaCraw community (https://surrealdb.com/community).

If you find a bug, submit an issue on GitHub (https://github.com/surrealdb/surrealdb/issues).

We would love it if you could star the repository (https://github.com/surrealdb/surrealdb).

----------
";

#[derive(Parser, Debug)]
#[command(name = "FudaCraw command-line interface and server", bin_name = "fudacraw")]
#[command(version = "RELEASE.as_str()", about = INFO, before_help = LOGO)]
#[command(disable_version_flag = false, arg_required_else_help = true)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
enum Commands {
    #[command(about = "Sync: Subscriber&Storage server of securities data")] Sync(
        SyncCommandArguments,
    ),
    #[command(about = "Syns: Publisher&Queryable server of securities data")] Syns(
        SynsCommandArguments,
    ),
    #[command(about = "Sse: Subscribe securities event data from finance websites")] Sse(
        SseCommandArguments,
    ),
    #[command(about = "His: Crawle securities history data from finance websites")] His(
        HisCommandArguments,
    ),

    #[command(about = "Qbt: Quantified Backtest for sample securites on all the strategies")] Qbt(
        QbtCommandArguments,
    ),

    #[command(
        about = "Qtr: Quantified Realtime Trading for selected securites with strategies"
    )] Qtr(QtrCommandArguments),
}

pub async fn init() -> ExitCode {
    println!("{LOGO}");

    // Start a new CPU profiler
    #[cfg(feature = "performance-profiler")]
    let guard = pprof::ProfilerGuardBuilder
        ::default()
        .frequency(1000)
        .blocklist(&["libc", "libgcc", "pthread", "vdso"])
        .build()
        .unwrap();
    // Parse the CLI arguments
    let args = Cli::parse();

    #[cfg(debug_assertions)]
    println!("{DEBUG_BUILD_WARNING}");

    // After version warning we can run the respective command
    let output = match args.command {
        Commands::His(args) => {
            let rs = commands::his::init(args).await;
            if rs.is_err() {
                error!("(Commands::His) rs={:?}", rs);
            }
            rs
        }

        Commands::Sse(args) => {
            let rs = commands::sse::init(args).await;
            if rs.is_err() {
                error!("(Commands::Sse) rs={:?}", rs);
            }
            rs
        }

        Commands::Sync(args) => {
            let rs = commands::sync::init(args).await;
            if rs.is_err() {
                error!("(Commands::Sync) rs={:?}", rs);
            }
            rs
        }

        Commands::Syns(args) => {
            let rs = commands::syns::init(args).await;
            if rs.is_err() {
                error!("(Commands::Sync) rs={:?}", rs);
            }
            rs
        }

        Commands::Qbt(args) => {
            let rs = commands::qbt::init(args).await;
            if rs.is_err() {
                error!("(Commands::Qbt) rs={:?}", rs);
            }
            rs
        }

        Commands::Qtr(args) => {
            let rs = commands::qtr::init(args).await;
            if rs.is_err() {
                error!("(Commands::Qtr) rs={:?}", rs);
            }
            rs
        }
    };
    // Save the flamegraph and profile
    #[cfg(feature = "performance-profiler")]
    if let Ok(report) = guard.report().build() {
        // Import necessary traits
        use pprof::protos::Message;
        use std::io::Write;
        // Output a flamegraph
        let file = std::fs::File::create("flamegraph.svg").unwrap();
        report.flamegraph(file).unwrap();
        // Output a pprof
        let mut file = std::fs::File::create("profile.pb").unwrap();
        let profile = report.pprof().unwrap();
        let mut content = Vec::new();
        profile.encode(&mut content).unwrap();
        file.write_all(&content).unwrap();
    }
    // Error and exit the programme
    if let Err(e) = output {
        error!("{}", e);
        ExitCode::FAILURE
    } else {
        ExitCode::SUCCESS
    }
}

#[derive(Debug)]
pub(crate) struct DbConfig {
    host: String,
    port: u16,
    username: String,
    password: String,
    namespace: String,
    database: String,
}

impl DbConfig {
    fn from_toml(config: &toml::Value) -> Result<Self> {
        Ok(DbConfig {
            host: config["database"]["host"]
                .as_str()
                .ok_or("Missing database host")
                .unwrap_or_default()
                .to_owned(),
            port: config["database"]["port"]
                .as_integer()
                .ok_or("Missing database port")
                .unwrap_or_default()
                .to_owned() as u16,
            username: config["database"]["username"]
                .as_str()
                .ok_or("Missing database username")
                .map_err(|e| anyhow!(e))
                .unwrap_or_default()
                .to_owned(),
            password: config["database"]["password"]
                .as_str()
                .ok_or("Missing database password")
                .map_err(|e| anyhow!(e))
                .unwrap_or_default()
                .to_owned(),
            namespace: config["database"]["namespace"]
                .as_str()
                .ok_or("Missing database namespace")
                .map_err(|e| anyhow!(e))
                .unwrap_or_default()
                .to_owned(),
            database: config["database"]["database"]
                .as_str()
                .ok_or("Missing database name")
                .map_err(|e| anyhow!(e))
                .unwrap_or_default()
                .to_owned(),
        })
    }
}
